using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using Felbot.Interaction;
using Felbot.Interface.Conversations;
using Felbot.Profiles;
using thTimer = System.Threading.Timer;
using thTimerCallback = System.Threading.TimerCallback;

namespace Felbot.Interface.Users {
    /// <summary>This object controls a list of users in the channel and channel events.</summary>
    public class Channel {
        #region Private Fields and Delegates
        private List<ChannelUser> m_Users = new List<ChannelUser>(1024);
        private string m_ChannelName;
        private int m_Order = 0;
        private BattleNetChannelFlags m_ChannelFlags = BattleNetChannelFlags.NotInChat;
        private ChannelUser m_You;
        /*
        private Button m_btnChoose;
        private Button m_btnCancel;
        private Button m_btnCreate;
        private Button m_btnDelete;
        private bool m_ChoosingCharacter = false;
         */
        private frmProfile m_frmProfile;
        private ListView m_lv;
        private Label m_lbl;
        // private BattleNetRealmPacketThread m_RealmThread;

        private delegate void ChannelDelegate();
        #endregion

        #region External Channel List Properties and Methods
        /// <summary>Gets the name of the current channel.</summary>
        public string ChannelName {
            get {
                return m_ChannelName;
            }
        }

        /// <summary>Gets the flags of the current channel.</summary>
        public BattleNetChannelFlags ChannelFlags {
            get {
                return m_ChannelFlags;
            }
        }

        /// <summary>Gets the ChannelListItem that represents the bot user.</summary>
        public ChannelUser You {
            get {
                return m_You;
            }
        }

        /// <summary>Gets a list of ChannelLsitItems that is the users in the channel.</summary>
        public List<ChannelUser> Users {
            get {
                List<ChannelUser> m_cliUsers = m_Users;
                m_cliUsers.TrimExcess();
                return m_cliUsers;
            }
        }

        /// <summary>Gets a user by username in channel. Perspective may affect this.</summary>
        /// <param name="username">The username.</param>
        /// <returns>A ChannelListItem user.</returns>
        public ChannelUser GetUser(string username) {
            foreach (ChannelUser m_liUser in m_Users) {
                if (m_liUser.Username.Equals(username, StringComparison.CurrentCultureIgnoreCase)) {
                    return m_liUser;
                }
            }
            return null;
        }

        /// <summary>Gets a user by real username in channel.</summary>
        /// <param name="username">The username.</param>
        /// <returns>A ChannelListItem user.</returns>
        public ChannelUser GetUserByRealName(string username) {
            foreach (ChannelUser m_liUser in m_Users) {
                if (m_liUser.RealUsername.Equals(username, StringComparison.CurrentCultureIgnoreCase)) {
                    return m_liUser;
                }
            }
            return null;
        }
        #endregion

        /// <summary>Creates a new instance of this class.</summary>
        /// <param name="main">The profile form that this is associated with.</param>
        /// <param name="label">The list view header label associated with the channel.</param>
        /// <param name="listview">The list view list assocaitated with the channel.</param>
        public Channel(frmProfile main, ListView listview, Label label) {
            m_frmProfile = main;
            m_lv = listview;
            m_lbl = label;

            InitSpamLogGCTimer();
        }

        #region Anti-Spam Hider
        private List<int> m_SpamLogInfracts = new List<int>();
        private Dictionary<string, int> m_SpamLogRepetition = new Dictionary<string, int>();
        private Dictionary<string, int> m_SpamLogUser = new Dictionary<string, int>();
        private Dictionary<int, DateTime> m_SpamLogInfractTimes = new Dictionary<int, DateTime>();
        private Dictionary<int, int> m_SpamLogInfractAmos = new Dictionary<int, int>();
        private Dictionary<int, double> m_SpamLogInfractends = new Dictionary<int, double>();
        private thTimer m_SpamLogGCTimer;

        // This is used to create anonymous methods for timer callbacks.
        private delegate void TimerCallbackDelegate(Object stateInfo);

        private void InitSpamLogGCTimer() {
            // Create the state moniting timer.
            m_SpamLogGCTimer = new thTimer(new thTimerCallback((TimerCallbackDelegate) delegate(Object stateInfo) {
                #region SpamLog Garbage Collection Timer
                DateTime now = DateTime.Now;
                int InfractNo = 0;

                for (int i = 0; i < m_SpamLogInfracts.Count; i++) {
                    InfractNo = m_SpamLogInfracts[i];

                    if (m_SpamLogInfractTimes.ContainsKey(InfractNo)) {
                        if (now.Subtract(m_SpamLogInfractTimes[InfractNo]).TotalSeconds > 200) {
                            m_SpamLogInfractTimes.Remove(InfractNo);
                            if (m_SpamLogInfractAmos.ContainsKey(InfractNo))
                                m_SpamLogInfractAmos.Remove(InfractNo);
                            if (m_SpamLogInfractends.ContainsKey(InfractNo)) 
                                m_SpamLogInfractends.Remove(InfractNo);
                            if (m_SpamLogRepetition.ContainsValue(InfractNo)) {
                                using (Dictionary<string, int>.KeyCollection.Enumerator enrepk = m_SpamLogRepetition.Keys.GetEnumerator())
                                using (Dictionary<string, int>.ValueCollection.Enumerator enrepv = m_SpamLogRepetition.Values.GetEnumerator()) {
                                    for (int j = 0; j < m_SpamLogRepetition.Count; j++) {
                                        enrepk.MoveNext(); enrepv.MoveNext();
                                        if (enrepv.Current == InfractNo) {
                                            m_SpamLogRepetition.Remove(enrepk.Current);
                                            break;
                                        }
                                    }
                                }
                            }
                            if (m_SpamLogUser.ContainsValue(InfractNo)) {
                                using (Dictionary<string, int>.KeyCollection.Enumerator enusrk = m_SpamLogUser.Keys.GetEnumerator())
                                using (Dictionary<string, int>.ValueCollection.Enumerator enusrv = m_SpamLogUser.Values.GetEnumerator()) {
                                    for (int j = 0; j < m_SpamLogUser.Count; j++) {
                                        enusrk.MoveNext(); enusrv.MoveNext();
                                        if (enusrv.Current == InfractNo) {
                                            m_SpamLogUser.Remove(enusrk.Current);
                                            break;
                                        }
                                    }
                                }
                            }
                            m_SpamLogInfracts.Remove(InfractNo);
                            i--;
                        } else {
                            if (m_SpamLogInfractends.ContainsKey(InfractNo))
                                m_SpamLogInfractends[InfractNo] *= 0.8;
                        }
                    }
                }
                #endregion
            }), null, 1000, 1000);
        }

        private int GetNextInfractNo() {
            for (int i = 0; i < m_SpamLogInfracts.Count; i++)
                if (m_SpamLogInfracts.Contains(i))
                    continue;
                else
                    return i;
            return m_SpamLogInfracts.Count;
        }

        private bool CheckChatSpam(string username, string text) {
            try {
                DateTime now = DateTime.Now;

                DateTime InfractTime;
                int InfractAmo;
                double Infractend;

                TimeSpan diff;
                double Diffend;

                int InfractNo = 0;
                int NextInfractNo = 0;
                bool InfractExists = false;
                bool IsSpam = false;
                int SpamLogInfractCount = m_SpamLogInfracts.Count;

                for (int i = 0; i < SpamLogInfractCount; i++) {
                    InfractNo = m_SpamLogInfracts[i];

                    InfractTime = m_SpamLogInfractTimes[InfractNo];
                    diff = now.Subtract(InfractTime);

                    InfractAmo = m_SpamLogInfractAmos[InfractNo];

                    Infractend = m_SpamLogInfractends[InfractNo];

                    Diffend = diff.TotalSeconds / InfractAmo;

                    if (m_SpamLogRepetition.ContainsKey(text) && m_SpamLogRepetition[text] == InfractNo) {
                        Infractend += text.Length / 100 / Diffend;
                        m_SpamLogInfractTimes[InfractNo] = now;
                        m_SpamLogInfractAmos[InfractNo]++;
                        m_SpamLogInfractends[InfractNo] = Infractend;
                        Console.WriteLine(
                                "infraction data:  rep [ " +
                                "#" + InfractNo + "  " +
                                "txt \"" + text + "\"  " +
                                "diff " + diff + "  " +
                                "amo " + InfractAmo + "  " +
                                "diffend " + Diffend + "  " +
                                "infractend " + Infractend + " ]");
                        InfractExists = true;
                        if (Infractend > 6)
                            IsSpam = true;

                        if (!m_SpamLogUser.ContainsKey(username)) {
                            NextInfractNo = GetNextInfractNo();
                            m_SpamLogInfracts.Add(NextInfractNo);
                            m_SpamLogUser.Add(username, NextInfractNo);
                            m_SpamLogInfractTimes.Add(NextInfractNo, now);
                            m_SpamLogInfractAmos.Add(NextInfractNo, 1);
                            m_SpamLogInfractends.Add(NextInfractNo, 1);
                            Console.WriteLine(
                                    "infraction data:  newusr [ " +
                                    "#" + NextInfractNo + "  " +
                                    "usr \"" + username + "\"  " +
                                    "diff 1  " +
                                    "amo 1  " +
                                    "diffend 1  " +
                                    "infractend 1 ]");
                        }
                    }

                    if (m_SpamLogUser.ContainsKey(username) && m_SpamLogUser[username] == InfractNo) {
                        Infractend += 1 / Diffend;
                        m_SpamLogInfractTimes[InfractNo] = now;
                        m_SpamLogInfractAmos[InfractNo]++;
                        m_SpamLogInfractends[InfractNo] = Infractend;
                        Console.WriteLine(
                                "infraction data:  usr [ " +
                                "#" + InfractNo + "  " +
                                "usr \"" + username + "\"  " +
                                "diff " + diff + "  " +
                                "amo " + InfractAmo + "  " +
                                "diffend " + Diffend + "  " +
                                "infractend " + Infractend + " ]");
                        InfractExists = true;
                        if (Infractend > 6)
                            IsSpam = true;

                        if (!m_SpamLogRepetition.ContainsKey(text)) {
                            NextInfractNo = GetNextInfractNo();
                            m_SpamLogInfracts.Add(NextInfractNo);
                            m_SpamLogRepetition.Add(text, NextInfractNo);
                            m_SpamLogInfractTimes.Add(NextInfractNo, now);
                            m_SpamLogInfractAmos.Add(NextInfractNo, 1);
                            m_SpamLogInfractends.Add(NextInfractNo, (double) text.Length / 100.0);
                            Console.WriteLine(
                                    "infraction data:  newrep [ " +
                                    "#" + NextInfractNo + "  " +
                                    "txt \"" + text + "\"  " +
                                    "diff 1  " +
                                    "amo 1  " +
                                    "diffend 1  " +
                                    "infractend " + text.Length / 100.0 + " ]");
                        }
                    }
                }

                if (!InfractExists) {
                    NextInfractNo = GetNextInfractNo();
                    m_SpamLogInfracts.Add(NextInfractNo);
                    m_SpamLogRepetition.Add(text, NextInfractNo);
                    m_SpamLogInfractTimes.Add(NextInfractNo, now);
                    m_SpamLogInfractAmos.Add(NextInfractNo, 1);
                    m_SpamLogInfractends.Add(NextInfractNo, (double) text.Length / 100.0);
                    Console.WriteLine(
                            "infraction data:  newrep [ " +
                            "#" + NextInfractNo + "  " +
                            "txt \"" + text + "\"  " +
                            "diff 1  " +
                            "amo 1  " +
                            "diffend 1  " +
                            "infractend " + text.Length / 100.0 + " ]");

                    NextInfractNo = GetNextInfractNo();
                    m_SpamLogInfracts.Add(NextInfractNo);
                    m_SpamLogUser.Add(username, NextInfractNo);
                    m_SpamLogInfractTimes.Add(NextInfractNo, now);
                    m_SpamLogInfractAmos.Add(NextInfractNo, 1);
                    m_SpamLogInfractends.Add(NextInfractNo, 1);
                    Console.WriteLine(
                            "infraction data:  newusr [ " +
                            "#" + NextInfractNo + "  " +
                            "usr \"" + username + "\"  " +
                            "diff 1  " +
                            "amo 1  " +
                            "diffend 1  " +
                            "infractend 1 ]");
                }

                return IsSpam;
            } catch (Exception) {
                return false;
            }
        }
        #endregion

        #region Channel Events
        /// <summary>
        /// Called on EID_SHOWUSER.
        /// </summary>
        public void OnShowUser(string username, string statstring, int flags, int ping) {
            string alias = PerspectiveParse(username);
            UserStats stats = new UserStats(statstring);
            if (ProfileControl.Global.GetSetting("Interface", "ShowUserInChannelMessages").ToBoolean(false)) {
                m_frmProfile.RichTextBox.AddTextNodes(
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, alias),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, " is in the channel using "),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, stats.ParsedStatString),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, "."));
            }
            AddUser(alias, username, stats, ping, flags);
        }

        /// <summary>
        /// Called on EID_JOIN
        /// </summary>
        public void OnJoin(string username, string statstring, int flags, int ping) {
            string alias = PerspectiveParse(username);
            UserStats stats = new UserStats(statstring);
            if (ProfileControl.Global.GetSetting("Interface", "ShowJoinLeaveMessages").ToBoolean(true)) {
                m_frmProfile.RichTextBox.AddTextNodes(
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, alias),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, " has joined the channel using "),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, stats.ParsedStatString),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, "."));
            }
            AddUser(alias, username, stats, ping, flags);
        }

        /// <summary>
        /// Called on EID_LEAVE
        /// </summary>
        public void OnLeave(string username) {
            string alias = PerspectiveParse(username);
            if (ProfileControl.Global.GetSetting("Interface", "ShowJoinLeaveMessages").ToBoolean(true)) {
                m_frmProfile.RichTextBox.AddTextNodes(
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, alias),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, " has left the channel."));
            }
            RemoveUser(username);
        }

        /// <summary>
        /// Called on EID_WHISPER
        /// </summary>
        public void OnWhisper(string username, string text) {
            if (!CheckChatSpam(username, text)) {
                string alias = PerspectiveParse(username); ;
                m_frmProfile.RichTextBox.AddTextNodes(
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_WhisperedFromName, alias),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_WhisperedFromText, text));
                CommandProcessor.ProcessCommand(
                    new CommandSource(CommandSourcePlace.Whisper, GetUser(username), m_frmProfile.Profile),
                    text);
            }
        }

        /// <summary>
        /// Called on EID_TALK
        /// </summary>
        public void OnTalk(string username, string text) {
            if (!CheckChatSpam(username, text)) {
                string alias = PerspectiveParse(username);
                m_frmProfile.RichTextBox.AddTextNodes(
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_UserName, alias),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_NormalText, text));
                CommandProcessor.ProcessCommand(
                    new CommandSource(CommandSourcePlace.Channel, GetUser(username), m_frmProfile.Profile),
                    text);
            }
        }

        /// <summary>
        /// Called on EID_BROADCAST
        /// </summary>
        public void OnBroadcast(string text) {
            m_frmProfile.RichTextBox.AddTextNodes(
                FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Info, "<Broadcast> " + text));
        }

        /// <summary>
        /// Called on EID_BROADCAST
        /// </summary>
        public void OnBroadcast(string username, string text) {
            string alias = PerspectiveParse(username);
            m_frmProfile.RichTextBox.AddTextNodes(
                FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Info, "<Broadcast: " + alias + "> " + text));
        }

        /// <summary>
        /// Called on EID_CHANNEL
        /// </summary>
        public void OnChannel(string channelName, byte channelFlags) {
            m_frmProfile.RichTextBox.AddTextNodes(
                FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, "Joined channel: "),
                FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, channelName));
            m_frmProfile.AddRecentChannel(channelName);
            JoinedChannel(channelName, channelFlags);
            m_Order = ProfileControl.Global.GetSetting("Interface", "OrderChannel").ToInteger(0);
        }

        /// <summary>
        /// Called on EID_USERFLAGS
        /// </summary>
        public void OnUserFlags(string username, string statstring, int flags, int ping) {
            string alias = PerspectiveParse(username);
            UserStats stats;
            if (statstring != "")
                stats = new UserStats(statstring);
            else
                stats = null;
            FlagsUpdate(alias, username, stats, ping, flags);
            if ((flags & 0x02) == 0x02) {
                if (username == m_frmProfile.PacketThread.UniqueUsername) {
                    m_frmProfile.RichTextBox.AddTextNodes(
                        FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, "You, "),
                        FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, alias),
                        FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, ", have ops."));
                } else {
                    m_frmProfile.RichTextBox.AddTextNodes(
                        FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Username, alias),
                        FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Status, " has ops."));
                }
            }
        }

        /// <summary>
        /// Called on EID_WHISPERSENT
        /// </summary>
        public void OnWhisperSent(string username, string text) {
            string alias = PerspectiveParse(username);
            m_frmProfile.RichTextBox.AddTextNodes(
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_WhisperedToName, alias),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_WhisperedToText, text));
        }

        /// <summary>
        /// Called on EID_INFO
        /// </summary>
        public void OnInfo(string text) {
            m_frmProfile.RichTextBox.AddTextNodes(
                FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Info, text));
        }

        /// <summary>
        /// Called on EID_ERROR
        /// </summary>
        public void OnError(string text) {
            m_frmProfile.RichTextBox.AddTextNodes(
                FCSControl.GetTextNode(FCSObjects.Chatroom_Information_Error, text));
        }

        /// <summary>
        /// Called on EID_EMOTE
        /// </summary>
        public void OnEmote(string username, string text) {
            if (!CheckChatSpam(username, text)) {
                string alias = PerspectiveParse(username);
                m_frmProfile.RichTextBox.AddTextNodes(
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_EmotedName, alias),
                    FCSControl.GetTextNode(FCSObjects.Chatroom_Talk_EmotedText, text));
            }
        }

        /// <summary>
        /// Called on EID_NAME or SID_ENTERCHAT
        /// </summary>
        public void OnEnterChat(string username, string statstring) {
            string alias = PerspectiveParse(username);
            UserStats stats = new UserStats(statstring);
            m_frmProfile.LoadQuickChannels();
            m_You = new ChannelUser(alias, username, stats, 0, 0);
        }
        #endregion

        #region User Information Changed
        /// <summary>
        /// Adds a user to the list.
        /// </summary>
        /// <param name="a_sUsername">Username</param>
        /// <param name="a_sRealUsername">Real username</param>
        /// <param name="a_ssStatstring">Statstring</param>
        /// <param name="a_iPing">Ping</param>
        /// <param name="a_iFlags">Flags</param>
        /// <param name="a_bInvisible">Invisible</param>
        private void AddUser(string username, string realUsername, UserStats stats, int ping, int flags, bool invisible) {
            m_frmProfile.Invoke((ChannelDelegate) delegate() {
                for (int j = 0; j < m_Users.Count; j++) {
                    if (m_Users[j].RealUsername.Equals(realUsername)) {
                        #region Update User in List
                        if (stats == null) {
                            if (m_Users[j].Username == username &&
                                m_Users[j].Flags == (BattleNetUserFlags) flags &&
                                m_Users[j].Invisible == invisible)
                                return;
                        } else {
                            if (m_Users[j].Username == username &&
                                m_Users[j].StatString.RawStatString == stats.RawStatString &&
                                m_Users[j].Ping == ping &&
                                m_Users[j].Flags == (BattleNetUserFlags) flags &&
                                m_Users[j].Invisible == invisible)
                                return;
                        }

                        m_Users[j].Username = username;
                        if (stats != null)
                            m_Users[j].SetStatString(stats.RawStatString);
                        m_Users[j].Ping = ping;
                        m_Users[j].Flags = (BattleNetUserFlags) flags;
                        m_Users[j].Invisible = invisible;
                        m_lv.Items[j] = GetListViewItem(m_Users[j]);
                        #endregion
                        return;
                    }
                }

                #region Add User to List
                ChannelUser user = new ChannelUser(username, realUsername, stats, ping, flags, invisible);
                m_Users.Add(user);
                AddToList(user);
                UpdateHeader();
                #endregion
            });
        }

        /// <summary>
        /// Adds a user to the list.
        /// </summary>
        /// <param name="a_sUsername">Username</param>
        /// <param name="a_sRealUsername">Real username</param>
        /// <param name="a_ssStatstring">Statstring</param>
        /// <param name="a_iPing">Ping</param>
        /// <param name="a_iFlags">Flags</param>
        private void AddUser(string username, string realUsername, UserStats stats, int ping, int flags) {
            AddUser(username, realUsername, stats, ping, flags, false);
        }

        /// <summary>
        /// This is called when EID_LEAVE occurs.
        /// Note: When EID_CHANNEL occurs, don't use this to clear the list.
        /// JoinedChannel takes care of that for you.
        /// </summary>
        /// <param name="a_sUsername">Username to remove</param>
        public void RemoveUser(string username) {
            m_frmProfile.Invoke((ChannelDelegate) delegate() {
                for (int i = 0; i < m_Users.Count; i++) {
                    if (m_Users[i].RealUsername.Equals(username)) {
                        m_Users.RemoveAt(i);
                        m_lv.Items.RemoveAt(i);
                        break;
                    }
                }
            });
        }

        /// <summary>
        /// This method is called when EID_USERFLAGS occurs.
        /// </summary>
        /// <param name="a_sUsername">Event's username</param>
        /// <param name="a_sRealUsername">User's real name.</param>
        /// <param name="a_sStatstring">User's statstring.</param>
        /// <param name="a_iPing">User's ping.</param>
        /// <param name="a_iFlags">New flags</param>
        public void FlagsUpdate(string username, string realUsername, UserStats stats, int ping, int flags) {
            for (int i = 0; i < m_Users.Count; i++) {
                if (m_Users[i].RealUsername.Equals(realUsername)) {
                    AddUser(username, realUsername, stats, ping, flags, m_Users[i].Invisible);
                    return;
                }
            }
            // they dont exist in channel, add as invisible
            AddUser(username, realUsername, stats, ping, flags, true);
        }
        #endregion

        #region Diablo II Character Selection
        /*
        /// <summary>Describes the CharacterList function.</summary>
        private delegate void CharacterList_Delegate(Dictionary<string, int> m_dictChars);

        /// <summary>
        /// Shows a list of characters to choose from when logging into the realm server.
        /// </summary>
        /// <param name="a_ptRealmThread">The realm thread.</param>
        /// <param name="a_dictChars">The character dictionary.</param>
        public void CharacterList(BattleNetRealmPacketThread a_ptRealmThread, Dictionary<string, int> a_dictChars) {
            m_RealmThread = a_ptRealmThread;
            m_frmProfile.Invoke((CharacterList_Delegate) delegate(Dictionary<string, int> m_dictChars) {
                m_frmProfile.tcChannel.SelectTab(0);
                ListViewItem m_lvItem; UserStatString m_ssStatstring; DateTime m_dtExpireTime; TimeSpan m_tsExpireTime;
                m_lbl.Text = " - Choose a Character - ";
                foreach (KeyValuePair<string, int> m_kvpChars in m_dictChars) {
                    m_ssStatstring = new UserStatString(m_kvpChars.Key);
                    m_lvItem = m_lv.Items.Add(m_ssStatstring.Diablo2.CharacterNamePrefix + m_ssStatstring.Diablo2.CharacterName);
                    m_lvItem.ToolTipText = m_ssStatstring.ParsedStatString.Substring(32, m_ssStatstring.ParsedStatString.Length - 33);
                    m_lvItem.ForeColor = Color.White;

                    if (m_ssStatstring.Diablo2.CharacterMode == Diablo2CharacterMode.Expansion) {
                        m_lvItem.ForeColor = Color.Gold;
                    }

                    if (m_ssStatstring.Diablo2.CharacterLadder) {
                        if (m_frmProfile.PacketThread.Client.Equals("D2DV")) {
                            m_lvItem.ForeColor = Color.Gray;
                        } else {
                            m_lvItem.ForeColor = Color.Lime;
                        }
                    }

                    m_dtExpireTime = DateTime.Parse("January 1 1970 00:00:00").AddSeconds(m_kvpChars.Value);
                    m_tsExpireTime = DateTime.Now.Subtract(m_dtExpireTime);
                    m_lvItem.ToolTipText += Environment.NewLine + m_tsExpireTime.Days.ToString() + " days until expiration (" + m_tsExpireTime.Seconds.ToString() + " seconds)";
                    if (m_tsExpireTime.Days < 10) {
                        m_lvItem.ForeColor = Color.Yellow;
                    }
                    if (m_tsExpireTime.TotalMilliseconds < 0) {
                        m_lvItem.ToolTipText += Environment.NewLine + "This character has expired.";
                        m_lvItem.ForeColor = Color.Red;
                    }
                }

                m_btnChoose = new Button();
                m_frmProfile.tpClan.Controls.Add(m_btnChoose);
                m_btnChoose.Name = "btnChoose";
                m_btnChoose.Text = "Choose";
                m_btnChoose.Enabled = false;
                m_btnChoose.UseVisualStyleBackColor = true;
                m_btnChoose.Size = new Size(100, 25);
                m_btnChoose.Location = new Point(m_lv.Parent.Size.Width - 210, m_lv.Parent.Size.Height - 30);
                m_btnChoose.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnChoose.Click += new EventHandler(btnChoose_Click);
                m_btnChoose.BringToFront();

                m_btnCancel = new Button();
                m_frmProfile.tpClan.Controls.Add(m_btnCancel);
                m_btnCancel.Name = "btnCancel";
                m_btnCancel.Text = "Cancel";
                m_btnCancel.UseVisualStyleBackColor = true;
                m_btnCancel.Size = new Size(100, 25);
                m_btnCancel.Location = new Point(m_lv.Parent.Size.Width - 105, m_lv.Parent.Size.Height - 30);
                m_btnCancel.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnCancel.Click += new EventHandler(btnCancel_Click);
                m_btnCancel.BringToFront();

                m_btnCreate = new Button();
                m_frmProfile.tpClan.Controls.Add(m_btnCreate);
                m_btnCreate.Name = "btnCreate";
                m_btnCreate.Text = "Create";
                m_btnCreate.Enabled = false;
                m_btnCreate.UseVisualStyleBackColor = true;
                m_btnCreate.Size = new Size(100, 25);
                m_btnCreate.Location = new Point(m_lv.Parent.Size.Width - 210, m_lv.Parent.Size.Height - 60);
                m_btnCreate.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnCreate.Click += new EventHandler(btnCreate_Click);
                m_btnCreate.BringToFront();

                m_btnDelete = new Button();
                m_frmProfile.tpClan.Controls.Add(m_btnCancel);
                m_btnDelete.Name = "btnDelete";
                m_btnDelete.Text = "Delete";
                m_btnDelete.Enabled = false;
                m_btnDelete.UseVisualStyleBackColor = true;
                m_btnDelete.Size = new Size(100, 25);
                m_btnDelete.Location = new Point(m_lv.Parent.Size.Width - 105, m_lv.Parent.Size.Height - 60);
                m_btnDelete.Anchor = ((AnchorStyles) AnchorStyles.Bottom | AnchorStyles.Right);
                m_btnDelete.Click += new EventHandler(btnDelete_Click);
                m_btnDelete.BringToFront();

                m_lv.SelectedIndexChanged += new EventHandler(ChannelListView_SelectedIndexChanged);
            }, a_ptRealmThread, a_dictChars);
        }

        private void ChannelListView_SelectedIndexChanged(object sender, EventArgs e) {
            if (!m_ChoosingCharacter)
                return;
            m_btnChoose.Enabled = (m_lv.SelectedItems.Count == 1);
            m_btnDelete.Enabled = (m_lv.SelectedItems.Count == 1);
        }

        private void btnCreate_Click(object sender, EventArgs e) {
            //bleh annoying!
        }

        private void btnDelete_Click(object sender, EventArgs e) {
            m_RealmThread.SendPacket0A(m_lv.SelectedItems[0].Text);
        }

        private void btnCancel_Click(object sender, EventArgs e) {
            m_RealmThread.Socket_Disconnect();
            m_frmProfile.PacketThread.RealmConnected(false);
            m_btnCreate.Dispose();
            m_btnDelete.Dispose();
            m_btnChoose.Dispose();
            m_btnCancel.Dispose();
            OutsideChat();
        }

        private void btnChoose_Click(object sender, EventArgs e) {
            m_RealmThread.SendPacket07(m_lv.SelectedItems[0].Text);
        }

        /// <summary>
        /// Removes a character from the list.
        /// </summary>
        /// <param name="m_sCharName">The character's name.</param>
        public void Deleted(string m_sCharName) {
            foreach (ListViewItem m_lvItem in m_lv.Items)
                if (m_lvItem.Text.Equals(m_sCharName, StringComparison.CurrentCultureIgnoreCase))
                    m_lvItem.Remove();
        }

        /// <summary>
        /// Selects a user from the lsit.
        /// </summary>
        /// <param name="m_sCharName">The character's name.</param>
        public void Chosen(string m_sCharName) {
            m_frmProfile.PacketThread.RealmConnected(true);
            m_btnCreate.Dispose();
            m_btnDelete.Dispose();
            m_btnChoose.Dispose();
            m_btnCancel.Dispose();
            OutsideChat();
        }
        */
        #endregion

        #region Channel List Header
        /// <summary>
        /// Cleans up the channel list when you log off.
        /// </summary>
        public void Disconnect() {
            m_frmProfile.Invoke((ChannelDelegate) delegate() {
                m_lv.Items.Clear();
                m_Users.Clear();
                UpdateHeader("Offline");
                m_ChannelName = "";
            });
        }

        /// <summary>
        /// Tells the user connecting...
        /// </summary>
        public void Connecting() {
            m_frmProfile.Invoke((ChannelDelegate) delegate() {
                UpdateHeader("Connecting...");
            });
        }

        /// <summary>
        /// Tells the user not in chat
        /// </summary>
        public void OutsideChat() {
            m_frmProfile.Invoke((ChannelDelegate) delegate() {
                m_lv.Items.Clear();
                m_Users.Clear();
                UpdateHeader("Not in Chat");
            });
        }

        /// <summary>
        /// This is called when EID_CHANNEL is received. It clears the channel
        /// list and sets the label above the channel list to indicate what
        /// channel the bot is in now.
        /// </summary>
        /// <param name="a_sChannelName">The channel that was joined</param>
        /// <param name="a_bChannelFlags">The channel flags.</param>
        public void JoinedChannel(string channelName, byte channelFlags) {
            m_ChannelName = channelName;
            m_ChannelFlags = (BattleNetChannelFlags) channelFlags;
            m_frmProfile.Invoke((ChannelDelegate) delegate() {
                m_lv.Items.Clear();
                m_Users.Clear();
                UpdateHeader();
            });
        }

        /// <summary>
        /// Changes the channel list header and form text when channel changes.
        /// </summary>
        private void UpdateHeader() {
            m_lbl.Text = " - " + ChannelName + " - " + Users.Count + " Users - ";

            StringBuilder tooltip = new StringBuilder();
            tooltip.AppendLine("Channel name: " + ChannelName);
            tooltip.AppendLine("Users in channel: " + Users.Count);
            m_lbl.Tag = (object) tooltip.ToString().Trim(Environment.NewLine.ToCharArray());

            m_frmProfile.Text = m_frmProfile.Profile.ProfileName +
                               " - Channel " + ChannelName +
                               " (" + Users.Count + ")" +
                               ((ChannelFlags & BattleNetChannelFlags.Silent) == BattleNetChannelFlags.Silent ? "(silent) " : "") +
                               (You == null ? "" : " as " + You.Username) +
                               " on " + m_frmProfile.PacketThread.Namespace;
        }

        /// <summary>
        /// Changes the channel list header and form text when channel changes.
        /// </summary>
        private void UpdateHeader(string text) {
            m_lbl.Text = " - " + text + " - ";

            m_lbl.Tag = (object) text;

            if (text == "Not in Chat")
                text += (You == null ? "" : " as " + You.Username) +
                           " on " + m_frmProfile.PacketThread.Namespace;

            m_frmProfile.Text = m_frmProfile.Profile.ProfileName +
                               " - " + text;
        }
        #endregion

        #region Channel List Item
        /// <summary>
        /// Gets a ListViewItem prepared for the channel list.
        /// </summary>
        /// <param name="a_sUsername">The alias.</param>
        /// <param name="a_sRealUsername">The real username.</param>
        /// <param name="a_ssStatstring">The statstring object.</param>
        /// <param name="a_iPing">The ping.</param>
        /// <param name="a_iFlags">The flags.</param>
        /// <param name="a_bInvisible">The state.</param>
        /// <returns>A ListViewItem fit to be inserted right into the channel list.</returns>
        private ListViewItem GetListViewItem(ChannelUser user) {
            ListViewItem item = new ListViewItem();

            string userNamespace = m_frmProfile.PacketThread.Namespace;
            string characterName = "";
            string userFlags = "0x0 (Normal)";
            string userStats = "(Unknown)";
            string userPing = "0ms";

            if (user.Username.Contains("@"))
                userNamespace = user.Username.Split('@')[1];

            if (user.Username.Contains("*")) {
                characterName = user.Username.Split('*')[0];
                user.Username = user.Username.Split('*')[1];
            }
            if ((user.StatString.ClientCode == "D2DV" || user.StatString.ClientCode == "D2XP") && user.StatString.Diablo2.OnRealm) {
                characterName = user.StatString.Diablo2.CharacterNamePrefix + user.StatString.Diablo2.CharacterName;
            }

            userFlags = "0x" + user.Flags.ToString("X") + " (" + ((BattleNetUserFlags) user.Flags).ToString() + ")";

            string m_sRawStats = user.StatString.RawStatString;
            if (m_sRawStats.Length > 25)
                m_sRawStats = m_sRawStats.Substring(0, 22) + " ...";
            userStats = m_sRawStats + " (" + user.StatString.ParsedStatString + ")";

            userPing = user.Ping.ToString() + "ms";

            StringBuilder tooltip = new StringBuilder();
            if (user.Username != user.RealUsername)
                tooltip.AppendLine("Actual name: " + user.RealUsername);
            tooltip.AppendLine("Namespace: " + userNamespace);
            if (characterName != "")
                tooltip.AppendLine("Character: " + characterName);
            tooltip.AppendLine("Statstring: " + userStats);
            tooltip.AppendLine("Flags: " + userFlags);
            tooltip.AppendLine("Ping: " + userPing);
            if (user.Invisible)
                tooltip.AppendLine("State: Hidden");
            item.ToolTipText = tooltip.ToString().Trim(Environment.NewLine.ToCharArray());

            Color m_cForeColor = Color.White;
            if ((user.Flags & BattleNetUserFlags.Squelched) == BattleNetUserFlags.Squelched)
                m_cForeColor = Color.Red;
            if (user.HasOps)
                m_cForeColor = Color.Gold;
            if ((user.Flags & BattleNetUserFlags.BlizzardRepresentative) == BattleNetUserFlags.BlizzardRepresentative)
                m_cForeColor = Color.RoyalBlue;
            if (user.Invisible)
                m_cForeColor = Color.Gray;
            item.ForeColor = m_cForeColor;

            if (user.RealUsername == m_frmProfile.PacketThread.UniqueUsername) {
                item.Font = new Font(item.Font, FontStyle.Bold);
                m_You = user;
            }

            item.Name = user.RealUsername;
            item.Text = user.Username;
            item.UseItemStyleForSubItems = false;
            item.SubItems.AddRange(
                new ListViewItem.ListViewSubItem[2] {
                new ListViewItem.ListViewSubItem(item, user.Ping.ToString() + (user.Ping > 100 ? "" : "ms"), GetPingColor(user.Ping), Color.Black, item.Font),
                new ListViewItem.ListViewSubItem(item, user.StatString.ClanTag, Color.White, Color.Black, item.Font)
            });

            item.ImageIndex = user.StatString.IconIndex;

            return item;
        }

        private void AddToList(ChannelUser newuser) {
            ListViewItem item = GetListViewItem(newuser);

            if (m_lv.Items.Count == 0) {
                m_lv.Items.Add(item);
                return;
            }

            int order = m_Order;
            ChannelUser user;
            string lastblizzrep = null;
            bool found = false;
            LinkedList<string> list = new LinkedList<string>();
            for (int i = 0; i <= m_Users.Count; i++) {
                if (i == m_Users.Count)
                    user = newuser;
                else
                    user = m_Users[i];
                switch (order) {
                    default:
                    case 0: //Hierarchal Join Order
                        if (user.Flags == BattleNetUserFlags.BlizzardRepresentative) {
                            list.AddFirst(user.RealUsername);
                            lastblizzrep = user.RealUsername;
                        } else if (user.HasOps) {
                            if (lastblizzrep == null)
                                list.AddFirst(user.RealUsername);
                            else
                                list.AddAfter(list.Find(lastblizzrep), user.RealUsername);
                        } else {
                            list.AddLast(user.RealUsername);
                        }
                        break;
                    case 1: //Join Order
                        list.AddLast(user.RealUsername);
                        break;
                    case 2: //Hierarchal Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.RealUsername,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == -1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.RealUsername);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.RealUsername);
                        break;
                    case 3: //Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.RealUsername,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == -1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.RealUsername);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.RealUsername);
                        break;
                    case 4: //Reverse Hierarchal Join Order
                        if (user.Flags == BattleNetUserFlags.BlizzardRepresentative) {
                            list.AddLast(user.RealUsername);
                            lastblizzrep = user.RealUsername;
                        } else if (user.HasOps) {
                            if (lastblizzrep == null)
                                list.AddLast(user.RealUsername);
                            else
                                list.AddBefore(list.Find(lastblizzrep), user.RealUsername);
                        } else {
                            list.AddFirst(user.RealUsername);
                        }
                        break;
                    case 5: //Reverse Join Order
                        list.AddFirst(user.RealUsername);
                        break;
                    case 6: //Reverse Hierarchal Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.RealUsername,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == 1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.RealUsername);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.RealUsername);
                        break;
                    case 7: //Reverse Alphabetical
                        using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                            for (int j = 0; j < list.Count; j++) {
                                listenumerator.MoveNext();
                                if (string.Compare(
                                        listenumerator.Current,
                                        newuser.RealUsername,
                                        CultureInfo.CurrentCulture,
                                        CompareOptions.IgnoreCase | CompareOptions.StringSort) == 1) {
                                    list.AddBefore(list.Find(listenumerator.Current), newuser.RealUsername);
                                    found = true;
                                    break;
                                }
                            }
                        }
                        if (!found)
                            list.AddLast(user.RealUsername);
                        break;
                }
            }

            using (LinkedList<string>.Enumerator listenumerator = list.GetEnumerator()) {
                for (int i = 0; i < list.Count; i++) {
                    listenumerator.MoveNext();
                    if (listenumerator.Current.Equals(newuser.RealUsername, StringComparison.CurrentCultureIgnoreCase)) {
                        m_lv.Items.Insert(i, item);
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Gets a color associated with a ping value.
        /// </summary>
        /// <param name="a_iPing">The ping as an integer.</param>
        /// <returns>A color that represents the ping value.</returns>
        private Color GetPingColor(int ping) {
            if (ping == 0)
                return Color.White;
            else if (ping > 0 && ping <= 512)
                return Color.FromArgb((int) ((ping - 1) / 2), 255, 0);
            else if (ping > 512 && ping <= 1024)
                return Color.FromArgb(255, (int) ((512 - (ping - 513)) / 2), 0);
            else
                return Color.Red;
        }

        /// <summary>
        /// Parses a stat string into a Stat String object, so it is easy to manage.
        /// </summary>
        /// <param name="a_sStatstring">The raw stat string.</param>
        /// <returns>The stat string object.</returns>
        private UserStats ParseStatstring(string statstring) {
            return new UserStats(statstring);
        }
        
        /// <summary>
        /// This method "parses" usernames to get the display name, using D2 Character/Username Display, Perspective, and Alias settings.
        /// </summary>
        /// <param name="a_sRealName">The original username.</param>
        /// <returns>The parsed username.</returns>
        public string PerspectiveParse(string realName) {
            if (m_frmProfile.PacketThread.Client.Equals("D2DV") || m_frmProfile.PacketThread.Client.Equals("D2XP")) {
                string character = realName.Split('*')[0];
                string username = realName.Split('*')[1];
                string mask;
                if (character.Length > 0)
                    mask = ProfileControl.Global.GetSetting("Interface", "D2StyleWithChar").ToString("%c (*%u)");
                else
                    mask = ProfileControl.Global.GetSetting("Interface", "D2StyleWithoutChar").ToString("*%u");
                realName = mask.Replace("%c", character).Replace("%u", username);
            }

            switch (ProfileControl.Global.GetSetting("Interface", "Perspective").ToInteger(0)) {
                default:
                case 0: //default
                    return realName;
                case 1: //warcraft iii
                    if (m_frmProfile.PacketThread.Client.Equals("WAR3") || m_frmProfile.PacketThread.Client.Equals("W3XP")) //no change needed
                        return realName;
                    else //flip everything
                        if (realName.EndsWith("@Blizzard")) //no change needed
                            return realName;
                        else if (realName.Contains("@")) //remove @gateway
                            return realName.Split('@')[0];
                        else //add @gateway
                            return realName + "@" + m_frmProfile.PacketThread.Namespace;
                case 2: //starcraft/diablo/diablo ii/warcraft ii/legacy
                    if (m_frmProfile.PacketThread.Client.Equals("WAR3") || m_frmProfile.PacketThread.Client.Equals("W3XP")) //flip everything
                        if (realName.EndsWith("@Blizzard")) //no change needed
                            return realName;
                        else if (realName.Contains("@")) //remove @gateway
                            return realName.Split('@')[0];
                        else //add @gateway
                            return realName + "@" + m_frmProfile.PacketThread.Namespace;
                    else //no change needed
                        return realName;
                case 3: //show all
                    if (!realName.Contains("@")) //add @gateway
                        return realName + "@" + m_frmProfile.PacketThread.Namespace;
                    else //no change needed
                        return realName;
                case 4: //hide all
                    return realName.Split('@')[0];
            }
        }
        #endregion
    }

    /// <summary>Represents a user in the channel list.</summary>
    public class ChannelUser {
        private string m_Username;
        private string m_RealUsername;
        private int m_Ping;
        private int m_Flags;
        private bool m_Invisible;
        private UserStats m_StatString;

        /// <summary>Gets their username. Perspective may affect this.</summary>
        public string Username {
            get {
                return m_Username;
            }
            internal set {
                m_Username = value;
            }
        }

        /// <summary>Gets their real username.</summary>
        public string RealUsername {
            get {
                return m_RealUsername;
            }
        }

        /// <summary>Gets their ping.</summary>
        public int Ping {
            get {
                return m_Ping;
            }
            internal set {
                m_Ping = value;
            }
        }

        /// <summary>Gets their user flags.</summary>
        public BattleNetUserFlags Flags {
            get {
                return (BattleNetUserFlags) m_Flags;
            }
            internal set {
                m_Flags = (int) value;
            }
        }

        /// <summary>Gets whether they were detected by EID_FLAGSUPDATE instead of by EID_JOIN.</summary>
        public bool Invisible {
            get {
                return m_Invisible;
            }
            internal set {
                m_Invisible = value;
            }
        }
        /// <summary>Gets their statstring object.</summary>
        public UserStats StatString {
            get {
                return m_StatString;
            }
        }

        /// <summary>Sets the users statstring object.</summary>
        /// <param name="a_sStatstring">The statstring as a string.</param>
        internal void SetStatString(string statstring) {
            if (statstring.Equals(""))
                return;
            try {
                m_StatString = new UserStats(statstring);
            } catch (Exception) {
                try {
                    m_StatString = new UserStats(statstring.Substring(0, 4));
                } catch (Exception) {
                    m_StatString = new UserStats("");
                }
            }
        }

        /// <summary>Tells whether they have ops based on user flags.</summary>
        public bool HasOps {
            get {
                return (Flags & BattleNetUserFlags.ChannelOperator) == BattleNetUserFlags.ChannelOperator;
            }
        }

        /// <summary>Creates a new channel list item.</summary>
        /// <param name="username">Their username. Perspective may affect this.</param>
        /// <param name="realUsername">Their real username.</param>
        /// <param name="stats">Their statstring as an object.</param>
        /// <param name="ping">Their ping.</param>
        /// <param name="flags">Their user flags.</param>
        /// <param name="invisible">Whether they are invisible.</param>
        public ChannelUser(string username, string realUsername, UserStats stats, int ping, int flags, bool invisible) {
            m_Username = username;
            m_RealUsername = realUsername;
            SetStatString(stats.RawStatString);
            m_Ping = ping;
            m_Flags = flags;
            m_Invisible = invisible;
        }

        /// <summary>Creates a new channel list item.</summary>
        /// <param name="a_sUsername">Their username. Perspective may affect this.</param>
        /// <param name="a_sRealUsername">Their real username.</param>
        /// <param name="a_ssStatstring">Their statstring as an object.</param>
        /// <param name="a_iPing">Their ping.</param>
        /// <param name="a_iFlags">Their user flags.</param>
        public ChannelUser(string username, string realUsername, UserStats stats, int ping, int flags)
            : this(username, realUsername, stats, ping, flags, false) {
        }
    }

    /// <summary>
    /// Specifies flags for Battle.net users.
    /// </summary>
    public enum BattleNetUserFlags : int {
        /// <summary>This user has no flags.</summary>
        Normal = 0x00,
        /// <summary>This user is a Blizzard representative.</summary>
        BlizzardRepresentative = 0x01,
        /// <summary>This user is a channel operator.</summary>
        ChannelOperator = 0x02,
        /// <summary>This user is a speaker. (DEFUNCT)</summary>
        Speaker = 0x04,
        /// <summary>This user is a Battle.net Administer. (DEFUNCT)</summary>
        BattleNetAdmin = 0x08,
        /// <summary>This user has a UDP Plug.</summary>
        UDPPlug = 0x10,
        /// <summary>This user is squelched.</summary>
        Squelched = 0x20,
        /// <summary>This user is a special guest. (DEFUNCT)</summary>
        SpecialGuest = 0x40,
        /// <summary>This user has beep enabled. (DEFUNCT)</summary>
        BeepEnabled = 0x100,
        /// <summary>This user is a PGL player. (DEFUNCT)</summary>
        PGLPlayer = 0x200,
        /// <summary>This user is a PGL official. (DEFUNCT)</summary>
        PGLOfficial = 0x400,
        /// <summary>This user is a KBK player. (DEFUNCT)</summary>
        KBKPlayer = 0x800,
        /// <summary>This user is a WCG official.</summary>
        WCGOfficial = 0x1000,
        /// <summary>This user is a KBK singles player. (DEFUNCT)</summary>
        KBKSingles = 0x2000,
        /// <summary>This user is a KBK beginner player. (DEFUNCT)</summary>
        KBKBeginner = 0x10000,
        /// <summary>This user is a white KBK player (1 bar). (DEFUNCT)</summary>
        KBKWhite1 = 0x20000,
        /// <summary>This user is a GF official.</summary>
        GFOfficial = 0x100000,
        /// <summary>This user is a GF player.</summary>
        GFPlayer = 0x200000,
        /// <summary>This user is a WCG player.</summary>
        WCGPlayer = 0x2000000
    }

    /// <summary>
    /// Specifies flags for Battle.net channels.
    /// </summary>
    public enum BattleNetChannelFlags : int {
        /// <summary>This channel has no flags.</summary>
        Normal = 0x00,
        /// <summary>This channel is public.</summary>
        Public = 0x01,
        /// <summary>This channel is moderated.</summary>
        Moderated = 0x02,
        /// <summary>This channel is restricted.</summary>
        Restricted = 0x04,
        /// <summary>This channel is silent.</summary>
        Silent = 0x08,
        /// <summary>This channel is a system channel.</summary>
        System = 0x10,
        /// <summary>This channel is a product-specific channel.</summary>
        ProductSpecific = 0x20,
        /// <summary>This channel is a globally-accessible channel.</summary>
        GloballyAccessible = 0x1000,
        /// <summary>The bot is offline or not in chat.</summary>
        NotInChat = -0x7FFFFFFF
    }
}